<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>vite介绍 | Blog</title>
    <meta name="generator" content="VuePress 1.9.10">
    <link rel="icon" href="/blog/img/logo.png">
    <link rel="manifest" href="/blog/manifest.json">
    <link rel="apple-touch-icon" href="/blog/icons/apple-touch-icon-152x152.png">
    <link rel="mask-icon" href="/blog/icons/safari-pinned-tab.svg" color="#3eaf7c">
    <meta name="description" content="blog | js | node | vue | vuepress | webpack">
    <meta name="theme-color" content="#3eaf7c">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <meta name="msapplication-TileImage" content="/icons/msapplication-icon-144x144.png">
    <meta name="msapplication-TileColor" content="#000000">
    
    <link rel="preload" href="/blog/assets/css/0.styles.b6b82f87.css" as="style"><link rel="preload" href="/blog/assets/js/app.43c5da00.js" as="script"><link rel="preload" href="/blog/assets/js/2.d3a12b6d.js" as="script"><link rel="preload" href="/blog/assets/js/1.f8bb34da.js" as="script"><link rel="preload" href="/blog/assets/js/31.665d02a7.js" as="script"><link rel="prefetch" href="/blog/assets/js/10.fde088a2.js"><link rel="prefetch" href="/blog/assets/js/11.c62b6b34.js"><link rel="prefetch" href="/blog/assets/js/12.27d4f152.js"><link rel="prefetch" href="/blog/assets/js/13.419d7748.js"><link rel="prefetch" href="/blog/assets/js/14.eb7a3d07.js"><link rel="prefetch" href="/blog/assets/js/15.114dfd5c.js"><link rel="prefetch" href="/blog/assets/js/16.85253907.js"><link rel="prefetch" href="/blog/assets/js/17.c2838453.js"><link rel="prefetch" href="/blog/assets/js/18.3256f17f.js"><link rel="prefetch" href="/blog/assets/js/19.d8afd0ae.js"><link rel="prefetch" href="/blog/assets/js/20.0d880388.js"><link rel="prefetch" href="/blog/assets/js/21.33b300c9.js"><link rel="prefetch" href="/blog/assets/js/22.4137860d.js"><link rel="prefetch" href="/blog/assets/js/23.b74cc977.js"><link rel="prefetch" href="/blog/assets/js/24.d99ad412.js"><link rel="prefetch" href="/blog/assets/js/25.ca9a652b.js"><link rel="prefetch" href="/blog/assets/js/26.66d6bac4.js"><link rel="prefetch" href="/blog/assets/js/27.95990a8e.js"><link rel="prefetch" href="/blog/assets/js/28.761c6ea4.js"><link rel="prefetch" href="/blog/assets/js/29.254ffe48.js"><link rel="prefetch" href="/blog/assets/js/3.af33e5d6.js"><link rel="prefetch" href="/blog/assets/js/30.6e4a155b.js"><link rel="prefetch" href="/blog/assets/js/32.45b461b5.js"><link rel="prefetch" href="/blog/assets/js/33.17a0771a.js"><link rel="prefetch" href="/blog/assets/js/34.af9f8240.js"><link rel="prefetch" href="/blog/assets/js/35.e9dd53af.js"><link rel="prefetch" href="/blog/assets/js/36.e900b84a.js"><link rel="prefetch" href="/blog/assets/js/37.52783c0e.js"><link rel="prefetch" href="/blog/assets/js/38.5cb7cb7f.js"><link rel="prefetch" href="/blog/assets/js/39.0d72378d.js"><link rel="prefetch" href="/blog/assets/js/4.0a92875f.js"><link rel="prefetch" href="/blog/assets/js/40.dfcfa312.js"><link rel="prefetch" href="/blog/assets/js/41.3ae8c9d7.js"><link rel="prefetch" href="/blog/assets/js/42.bed07f3e.js"><link rel="prefetch" href="/blog/assets/js/43.170da95d.js"><link rel="prefetch" href="/blog/assets/js/44.eec82bc0.js"><link rel="prefetch" href="/blog/assets/js/45.b5a61f3a.js"><link rel="prefetch" href="/blog/assets/js/46.dbdb669f.js"><link rel="prefetch" href="/blog/assets/js/47.f3b25bee.js"><link rel="prefetch" href="/blog/assets/js/48.6c3ba867.js"><link rel="prefetch" href="/blog/assets/js/49.2c3070ea.js"><link rel="prefetch" href="/blog/assets/js/5.3b9be48d.js"><link rel="prefetch" href="/blog/assets/js/50.4a2a7438.js"><link rel="prefetch" href="/blog/assets/js/51.b3b493a3.js"><link rel="prefetch" href="/blog/assets/js/52.a56b61d7.js"><link rel="prefetch" href="/blog/assets/js/53.8f36ae1d.js"><link rel="prefetch" href="/blog/assets/js/54.8ee09618.js"><link rel="prefetch" href="/blog/assets/js/55.527c5d9c.js"><link rel="prefetch" href="/blog/assets/js/56.a09690f4.js"><link rel="prefetch" href="/blog/assets/js/57.cf4bc588.js"><link rel="prefetch" href="/blog/assets/js/58.0c8b9d19.js"><link rel="prefetch" href="/blog/assets/js/59.7d09c3f1.js"><link rel="prefetch" href="/blog/assets/js/6.7b96ebf4.js"><link rel="prefetch" href="/blog/assets/js/60.52e5c6db.js"><link rel="prefetch" href="/blog/assets/js/61.afa66052.js"><link rel="prefetch" href="/blog/assets/js/62.d7a75678.js"><link rel="prefetch" href="/blog/assets/js/63.77765300.js"><link rel="prefetch" href="/blog/assets/js/64.9777d386.js"><link rel="prefetch" href="/blog/assets/js/65.deeca199.js"><link rel="prefetch" href="/blog/assets/js/66.aba723e9.js"><link rel="prefetch" href="/blog/assets/js/67.47b5088b.js"><link rel="prefetch" href="/blog/assets/js/68.3178dffd.js"><link rel="prefetch" href="/blog/assets/js/69.a0f4810a.js"><link rel="prefetch" href="/blog/assets/js/7.7e58b9c4.js"><link rel="prefetch" href="/blog/assets/js/70.5b120c62.js"><link rel="prefetch" href="/blog/assets/js/71.2335838f.js"><link rel="prefetch" href="/blog/assets/js/vendors~docsearch.2493936b.js">
    <link rel="stylesheet" href="/blog/assets/css/0.styles.b6b82f87.css">
  </head>
  <body>
    <div id="app" data-server-rendered="true"><div class="theme-container"><header class="navbar"><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div> <a href="/blog/" class="home-link router-link-active"><!----> <span class="site-name">Blog</span></a> <div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""> <!----></div> <nav class="nav-links can-hide"><div class="nav-item"><a href="/blog/" class="nav-link">
  home
</a></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="long-ui" class="dropdown-title"><span class="title">long-ui</span> <span class="arrow down"></span></button> <button type="button" aria-label="long-ui" class="mobile-dropdown-title"><span class="title">long-ui</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/blog/ui/" class="nav-link">
  UI简介
</a></li><li class="dropdown-item"><!----> <a href="https://weberlong.github.io/long-ui/" target="_blank" rel="noopener noreferrer" class="nav-link external">
  预览
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li><li class="dropdown-item"><!----> <a href="https://github.com/WeberLong/long-ui" target="_blank" rel="noopener noreferrer" class="nav-link external">
  源码
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li></ul></div></div><div class="nav-item"><a href="/blog/archives/" class="nav-link router-link-active">
  archives
</a></div><div class="nav-item"><a href="/blog/interview/" class="nav-link">
  interview
</a></div> <a href="https://github.com/WeberLong" target="_blank" rel="noopener noreferrer" class="repo-link">
    GitHub
    <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></nav></div></header> <div class="sidebar-mask"></div> <aside class="sidebar"><nav class="nav-links"><div class="nav-item"><a href="/blog/" class="nav-link">
  home
</a></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="long-ui" class="dropdown-title"><span class="title">long-ui</span> <span class="arrow down"></span></button> <button type="button" aria-label="long-ui" class="mobile-dropdown-title"><span class="title">long-ui</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/blog/ui/" class="nav-link">
  UI简介
</a></li><li class="dropdown-item"><!----> <a href="https://weberlong.github.io/long-ui/" target="_blank" rel="noopener noreferrer" class="nav-link external">
  预览
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li><li class="dropdown-item"><!----> <a href="https://github.com/WeberLong/long-ui" target="_blank" rel="noopener noreferrer" class="nav-link external">
  源码
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li></ul></div></div><div class="nav-item"><a href="/blog/archives/" class="nav-link router-link-active">
  archives
</a></div><div class="nav-item"><a href="/blog/interview/" class="nav-link">
  interview
</a></div> <a href="https://github.com/WeberLong" target="_blank" rel="noopener noreferrer" class="repo-link">
    GitHub
    <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></nav>  <ul class="sidebar-links"><li><section class="sidebar-group depth-0"><a href="/blog/archives/" class="sidebar-heading clickable router-link-active open"><span>我的文章</span> <!----></a> <ul class="sidebar-links sidebar-group-items"><li><a href="/blog/archives/component.html" class="sidebar-link">Vue组件开发</a></li><li><a href="/blog/archives/webpack.html" class="sidebar-link">Webpack简介</a></li><li><a href="/blog/archives/rtlcss.html" class="sidebar-link">阿拉伯语右排布局</a></li><li><a href="/blog/archives/monitor.html" class="sidebar-link">性能与错误监控方案</a></li><li><a href="/blog/archives/performance.html" class="sidebar-link">前端性能优化</a></li><li><a href="/blog/archives/recode.html" class="sidebar-link">Docs重构</a></li><li><a href="/blog/archives/cach.html" class="sidebar-link">理解浏览器缓存</a></li><li><a href="/blog/archives/httpcode.html" class="sidebar-link">HTTP 状态码</a></li><li><a href="/blog/archives/vite.html" aria-current="page" class="active sidebar-link">vite介绍</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/blog/archives/vite.html#一、vite简介" class="sidebar-link">一、Vite简介</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/blog/archives/vite.html#_1-1、什么是vite" class="sidebar-link">1.1、什么是Vite</a></li><li class="sidebar-sub-header"><a href="/blog/archives/vite.html#_1-2-、vite的主要特性" class="sidebar-link">1.2 、Vite的主要特性</a></li><li class="sidebar-sub-header"><a href="/blog/archives/vite.html#_1-3、-主流构建工具对比" class="sidebar-link">1.3、 主流构建工具对比</a></li><li class="sidebar-sub-header"><a href="/blog/archives/vite.html#_1-4、-为什么要使用vite" class="sidebar-link">1.4、 为什么要使用Vite</a></li><li class="sidebar-sub-header"><a href="/blog/archives/vite.html#_1-5、-vite-开发环境-vs-生产环境" class="sidebar-link">1.5、 Vite 开发环境 VS 生产环境</a></li></ul></li><li class="sidebar-sub-header"><a href="/blog/archives/vite.html#二、vite原理" class="sidebar-link">二、Vite原理</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/blog/archives/vite.html#_2-1-、esm-esbuild" class="sidebar-link">2.1 、ESM&amp;esbuild</a></li><li class="sidebar-sub-header"><a href="/blog/archives/vite.html#_2-2、请求拦截" class="sidebar-link">2.2、请求拦截</a></li><li class="sidebar-sub-header"><a href="/blog/archives/vite.html#_2-3、-热更新原理" class="sidebar-link">2.3、 热更新原理</a></li></ul></li><li class="sidebar-sub-header"><a href="/blog/archives/vite.html#三、问题" class="sidebar-link">三、问题</a></li></ul></li></ul></section></li></ul> </aside> <main class="page"> <div class="theme-default-content content__default"><h1 id="vite介绍"><a href="#vite介绍" class="header-anchor">#</a> vite介绍</h1> <h2 id="一、vite简介"><a href="#一、vite简介" class="header-anchor">#</a> 一、Vite简介</h2> <h3 id="_1-1、什么是vite"><a href="#_1-1、什么是vite" class="header-anchor">#</a> 1.1、什么是Vite</h3> <blockquote><p>Vite是一种新型的前端构建工具，它能显著改善前端开发体验。</p></blockquote> <p>Vite由两个主要部分组成：</p> <ol><li>dev server：利用浏览器的ESM能力来提供源文件，具有丰富的内置功能并具有高效的HMR</li> <li>生产构建：生产环境利用Rollup来构建代码，提供指令用来优化构建过程</li></ol> <p>Vite作为一个基于浏览器原生ESM的构建工具，它省略了开发环境的打包过程，利用浏览器去解析imports，在服务端按需编译返回。同时，在开发环境拥有速度快到惊人的模块热更新，且热更新的速度不会随着模块增多而变慢。因此，使用Vite进行开发，至少会比Webpack快10倍左右。</p> <h3 id="_1-2-、vite的主要特性"><a href="#_1-2-、vite的主要特性" class="header-anchor">#</a> 1.2 、Vite的主要特性</h3> <ul><li>Instant Server Start —— 即时服务启动</li> <li>Lightning Fast HMR —— 闪电般快速的热更新</li> <li>Rich Features —— 丰富的功能</li> <li>Optimized Build —— 经过优化的构建</li> <li>Universal Plugin Interface —— 通用的Plugin接口</li> <li>Fully Typed APIs —— 类型齐全的API</li></ul> <h3 id="_1-3、-主流构建工具对比"><a href="#_1-3、-主流构建工具对比" class="header-anchor">#</a> 1.3、 主流构建工具对比</h3> <p>构建工具指能自动对代码执行检验、转换、压缩等功能的工具。常见功能包括：代码转换、代码打包、代码压缩、HMR、代码检验。构建工具也随着前端技术的发展，从Browserify、Gulp到Parcel，从Webpack到Rollup，一直到最近比较火的面向非打包的Snowpack和Vite。</p> <p><strong>Browserify</strong></p> <ul><li>预编译模块化方案（文件打包工具）</li> <li>Browserify基于流方式干净灵活</li> <li>遵循commonJS规范打包JS</li> <li>可引入插件打包CSS等其他资源（非原生能力）</li></ul> <p><strong>Gulp</strong></p> <ul><li>基于流的自动化构建工具（工程化）</li> <li>配置复杂度高，偏向编程式，需要定义task处理构建</li> <li>支持监听读写文件</li> <li>可搭配Browserify等模块化工具来使用</li></ul> <p><strong>Parcel</strong></p> <ul><li>极速打包（工程化：极速0配置）</li> <li>零配置，但造成了配置不灵活，内置常见场景的构建方案及其依赖，无需再次安装（babel等）</li> <li>以html入口，自动检测和打包依赖</li> <li>不支持SourceMap</li> <li>无法Tree-shaking</li></ul> <p><strong>Webpack</strong></p> <ul><li>预编译模块化方案（工程化：大而全）</li> <li>通过配置文件达到一站式配置</li> <li>loader进行资源转换，功能全面（css+js+icon+front）</li> <li>插件丰富，灵活扩展</li> <li>社群庞大</li> <li>大型项目构建慢</li></ul> <p><strong>Rollup</strong></p> <ul><li>基于ES6打包（模块打包工具）</li> <li>Tree-shaking</li> <li>打包文件小且干净，执行效率更高</li> <li>更专注于JS打包</li></ul> <p><strong>Snowpack</strong></p> <ul><li>基于ESM运行时编译（工程化：ESM运行时）</li> <li>无需递归循环依赖组装依赖树</li> <li>默认输出单独的构建模块（未打包），可选择不同打包器（webpack、rollup等）</li></ul> <p><strong>Vite</strong></p> <ul><li>基于ESM运行时打包</li> <li>借鉴了Snowpack</li> <li>生产环境使用Rollup，集成度更高，相比Snowpack支持多页面、库模式、动态导入自动polyfill等</li></ul> <h3 id="_1-4、-为什么要使用vite"><a href="#_1-4、-为什么要使用vite" class="header-anchor">#</a> 1.4、 为什么要使用Vite</h3> <p><strong>开发环境速度的提升</strong></p> <p>经过1.3节，我们简单对比了各打包工具之间的差异。可以看到使用JS开发的工具通常需要很长的时间才能启动开发服务器，且这个启动时间与代码量、代码复杂度正相关。即使使用HMR，文件修改后的效果也要几秒钟才能在浏览器中反应出来，代表如Webpack。那么Vite是如何解决如Webpack这样的构建工具一样，在复杂、多模块项目开发中启动慢、HMR慢的问题呢？</p> <p>我们详细对比了开发环境中的Vite和Webpack，发现主要有如下不同：</p> <table><thead><tr><th>Webpack</th> <th>Vite</th></tr></thead> <tbody><tr><td>先打包生成bundle，再启动开发服务器</td> <td>先启动开发服务器，利用新一代浏览器的ESM能力，无需打包，直接请求所需模块并实时编译</td></tr> <tr><td>HMR时需要把改动模块及相关依赖全部编译</td> <td>HMR时只需让浏览器重新请求该模块，同时利用浏览器的缓存（源码模块协商缓存，依赖模块强缓存）来优化请求</td></tr> <tr><td>内存高效利用</td> <td>-</td></tr></tbody></table> <p>因此，针对开发环境中的启动慢问题，Vite开发环境冷启动无需打包，无需分析模块之间的依赖，同时也无需在启动开发服务器前进行编译，启动时还会使用esbuild来进行预构建。而Webpack 启动后会做一堆事情，经历一条很长的编译打包链条，从入口开始需要逐步经历语法解析、依赖收集、代码转译、打包合并、代码优化，最终将高版本的、离散的源码编译打包成低版本、高兼容性的产物代码，这可满满都是 CPU、IO 操作啊，在 Node 运行时下性能必然是有问题。</p> <p>针对HMR慢，即使只有很小的改动，Webpack依然需要构建完整的模块依赖图，并根据依赖图来进行转换。而Vite利用了ESM和浏览器缓存技术，更新速度与项目复杂度无关。可以看到，如Snowpack、Vite这类面相非打包的构建工具，在开发环境启动时只需要启动两个Server，一个用于页面加载，一个用于HMR的Websocket。当浏览器发出原生的ESM请求，Server收到请求只需要编译当前文件后返回给浏览器，不需要管理依赖。</p> <p><img src="https://pic1.zhimg.com/80/v2-88b66eb443afbbb47081b99447c3f744_1440w.webp" alt="img"></p> <p><img src="https://pic3.zhimg.com/80/v2-be21ccda836ca3ff547af801b3fae9f2_1440w.webp" alt="img"></p> <p><strong>使用简单，开箱即用</strong></p> <p>相比Webpack需要对entry、loader、plugin等进行诸多配置，Vite的使用可谓是相当简单了。只需执行初始化命令，就可以得到一个预设好的开发环境，开箱即获得一堆功能，包括：CSS预处理、html预处理、异步加载、分包、压缩、HMR等。他使用复杂度介于Parcel和Webpack的中间，只是暴露了极少数的配置项和plugin接口，既不会像Parcel一样配置不灵活，又不会像Webpack一样需要了解庞大的loader、plugin生态，灵活适中、复杂度适中。适合前端新手。</p> <h3 id="_1-5、-vite-开发环境-vs-生产环境"><a href="#_1-5、-vite-开发环境-vs-生产环境" class="header-anchor">#</a> 1.5、 Vite 开发环境 VS 生产环境</h3> <p>在1.3节主流工具对比时我们可以看到Vite的开发环境和生产环境具有较大的差异性。</p> <p>开发环境不需要对所有资源打包，只是使用esbuild对依赖进行预构建，将CommonJS和UMD发布的依赖转换为浏览器支持的ESM，同时提高了后续页面的加载性能（lodash的请求）。Vite会将于构建的依赖缓存到node_modules/.vite目录下，它会根据几个源来决定是否需要重新运行预构建，包括 packages.json中的dependencies列表、包管理器的lockfile、可能在vite.config.js相关字段中配置过的。只要三者之一发生改变，才会重新预构建。</p> <p>同时，开发环境使用了浏览器缓存技术，解析后的依赖请求以http头的max-age=31536000,immutable强缓存，以提高页面性能。</p> <p>在生产环境，由于嵌套导入会导致发送大量的网络请求，即使使用HTTP2.x（多路复用、首部压缩），在生产环境中发布未打包的ESM仍然性能低下。因此，对比在开发环境Vite使用esbuild来构建依赖，生产环境Vite则使用了更加成熟的Rollup来完成整个打包过程。因为esbuild虽然快，但针对应用级别的代码分割、CSS处理仍然不够稳定，同时也未能兼容一些未提供ESM的SDK。</p> <p>为了在生产环境中获得最佳的加载性能，仍然需要对代码进行tree-shaking、懒加载以及chunk分割（以获得更好的缓存）。</p> <h2 id="二、vite原理"><a href="#二、vite原理" class="header-anchor">#</a> 二、Vite原理</h2> <h3 id="_2-1-、esm-esbuild"><a href="#_2-1-、esm-esbuild" class="header-anchor">#</a> 2.1 、ESM&amp;esbuild</h3> <p><strong>ESM</strong></p> <p>在ES6没有出现之前，随着js代码日益膨胀，往往会对资源模块化来提效，这也就出现了多个模块化方案。如CommonJS常用于服务端，AMD、CMD规范常用在客户端。ES6出现后，紧接着出现了ESM。ESM是浏览器支持的一种模块化方案，允许在浏览器实现模块化。</p> <ul><li>CommonJS：模块同步，如Browserify会对代码进行解析，整理出代码中的所有模块依赖关系，然后把nodejs的模块编译成浏览器可用的模块，相关的模块代码都打包在一起，形成一个完整的JS文件，这个文件中不会存在 require 这类的模块化语法，变成可以在浏览器中运行的普通JS，运行时加载</li> <li>AMD：模块异步，依赖前置，是requireJS在推广过程中对模块定义的规范化产出，加载完依赖后立即执行依赖模块，依赖加载成功后执行回调</li> <li>CMD：模块异步，延迟执行，是seaJS在推广过程中对模块定义的规范化产出，就近依赖，先加载所有依赖模块，运行时才执行require内容，按顺序执行</li></ul> <p>与CommonJS、AMD不同，ESM的对外接口只是一种静态定义，为编译时加载，遇到模块加载命令import，就会生成一个只读引用。等脚本真正执行时，再根据这个只读引用，到被加载的那个模块内取值。由于ESM编译时就能确定模块的依赖关系，因此能够只包含要运行的代码，可以显著减少文件体积，降低浏览器压力。</p> <p>由于ESM是一个比较新的模块化方案，目前其浏览器能力支持如下：</p> <p><img src="https://pic1.zhimg.com/80/v2-0bc8447d2a0dfb349e8446f8895d7664_1440w.webp" alt="img"></p> <p>可以看到，除了IE、Opera等，新一代浏览器中绝大部分都已支持。</p> <p>接下来以Vite创建的模板为例，看一下ESM的解析过程：</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token operator">&lt;</span>template<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>img alt<span class="token operator">=</span><span class="token string">&quot;Vue logo&quot;</span> src<span class="token operator">=</span><span class="token string">&quot;./assets/logo.png&quot;</span> <span class="token operator">/</span><span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>HelloWorld msg<span class="token operator">=</span><span class="token string">&quot;Hello Vue 3.0 + Vite&quot;</span> <span class="token operator">/</span><span class="token operator">&gt;</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>template<span class="token operator">&gt;</span>

<span class="token operator">&lt;</span>script<span class="token operator">&gt;</span>
  <span class="token keyword">import</span> HelloWorld <span class="token keyword">from</span> <span class="token string">'./components/HelloWorld.vue'</span>
  <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'App'</span><span class="token punctuation">,</span>
    <span class="token literal-property property">components</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      HelloWorld
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>script<span class="token operator">&gt;</span>
</code></pre></div><p>当浏览器解析 import HelloWorld from './components/HelloWorld.vue' 时，会向当前域名发送一个请求获取对应的资源（ESM支持解析相对路径）。</p> <p><img src="https://pic4.zhimg.com/80/v2-9da3bc762f10001601a474505d58f40b_1440w.webp" alt="img"></p> <p>浏览器下载对应的文件，然后解析成模块记录。接下来会进行实例化，为模块分配内存，然后按照导入、导出语句建立模块和内存的映射关系。最后，运行上述代码，把内存空间填充为真实的值。</p> <p><strong>esbuild</strong></p> <p>Vite 对 js/ts 的处理没有使用如 glup, rollup 等传统打包工具，而是使用了 esbuild。esbuild 是一个全新的js打包工具，底层使用了go，大量使用了并行操作，可以充分利用CPU资源。esbuild支持如babel, 压缩等的功能。</p> <p>对比各打包工具性能，可以看到esbuild比rollup等工具快十几倍。</p> <p><img src="https://pic4.zhimg.com/80/v2-789b4a555f8f5142cf7ea5b43676cadb_1440w.webp" alt="img"></p> <h3 id="_2-2、请求拦截"><a href="#_2-2、请求拦截" class="header-anchor">#</a> 2.2、请求拦截</h3> <p>Vite 的基本实现原理，就是启动一个 koa 服务器拦截由浏览器请求 ESM的请求。通过请求的路径找到目录下对应的文件做一定的处理最终以 ESM的格式返回给客户端。</p> <p><img src="https://pic2.zhimg.com/80/v2-e3852ed76ec402c75f06c8d6ec8921d1_1440w.webp" alt="img"></p> <p><strong>2.2.1、依赖处理</strong></p> <p>Vite 通过在一开始将应用中的模块区分为 <strong>依赖</strong> 和 <strong>源码</strong> 两类，改进了开发服务器启动时间。<strong>依赖</strong> 大多为在开发时不会变动的纯 JavaScript。一些较大的依赖（例如有上百个模块的组件库）处理的代价也很高。</p> <ul><li><strong>依赖解析</strong></li></ul> <p>以 Vite 官方 demo 为例，当我们请求 <strong>localhost:3000</strong> 时，Vite 默认返回 <strong>localhost:3000/index.html</strong> 的代码。而后发送请求 <strong>src/main.js</strong>。</p> <p>main.js 代码如下：</p> <div class="language-text extra-class"><pre class="language-text"><code>import { createApp } from 'vue'
import App from './App.vue'
import './index.css'

createApp(App).mount('#app')
</code></pre></div><p><img src="https://pic2.zhimg.com/80/v2-dff3934e3d5d0f45359678d47cd38651_1440w.webp" alt="img"></p> <p>可以观察到浏览器请求 vue.js 时， 请求路径是 <strong>@modules/vue.js</strong>。在 Vite 中约定若 path 的请求路径满足 <strong>/^/@modules//</strong> 格式时，被认为是一个 node_modules 模块。</p> <p>平时开发中，webpack &amp; rollup(rollup有对应插件) 等打包工具会帮我们找到模块的路径，但浏览器只能通过相对路径去寻找，而如果是直接使用模块名比如：<strong>import vue from 'vue'</strong>，浏览器就会报错，这个时候就需要一个三方包进行处理。Vite 对ESM形式的 js 文件模块使用了 ES Module Lexer 处理。Lexer 会找到代码中以 import 语法导入的模块并以数组形式返回。Vite 通过该数组的值获取判断是否为一个 node_modules 模块。若是则进行对应改写成 @modules/:id 的写法。</p> <p>重写完路径后，浏览器会发送 path 为 /@modules/:id 的对应请求，接下来会被 Vite 客户端做一层拦截来解析模块的真实位置。</p> <p>首先正则匹配请求路径，如果是/@modules开头就进行后续处理，否则就跳过。若是，会设置响应类型为js，读取真实模块路径内容，返回给客户端。</p> <p>客户端注入本质上是创建一个script标签（type='module'），然后将其插入到head中，这样客户端在解析html是就可以执行代码了</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">export</span> <span class="token keyword">const</span> moduleRE <span class="token operator">=</span> <span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">^\/@modules\/</span><span class="token regex-delimiter">/</span></span>
<span class="token comment">// plugin for resolving /@modules/:id requests.</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>moduleRE<span class="token punctuation">.</span><span class="token function">test</span><span class="token punctuation">(</span>ctx<span class="token punctuation">.</span>path<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
  <span class="token comment">// path maybe contain encode chars</span>
  <span class="token keyword">const</span> id <span class="token operator">=</span> <span class="token function">decodeURIComponent</span><span class="token punctuation">(</span>ctx<span class="token punctuation">.</span>path<span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span>moduleRE<span class="token punctuation">,</span> <span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
  ctx<span class="token punctuation">.</span>type <span class="token operator">=</span> <span class="token string">'js'</span>
  <span class="token keyword">const</span> <span class="token function-variable function">serve</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token literal-property property">id</span><span class="token operator">:</span> string<span class="token punctuation">,</span> <span class="token literal-property property">file</span><span class="token operator">:</span> string<span class="token punctuation">,</span> <span class="token literal-property property">type</span><span class="token operator">:</span> string</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token comment">// 在代码中做一个缓存，下次访问相同路径直接从 map 中获取 304 返回</span>
    moduleIdToFileMap<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> file<span class="token punctuation">)</span>
    moduleFileToIdMap<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>file<span class="token punctuation">,</span> ctx<span class="token punctuation">.</span>path<span class="token punctuation">)</span>
    <span class="token function">debug</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">(</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>type<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">) </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>id<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> -&gt; </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">getDebugPath</span><span class="token punctuation">(</span>root<span class="token punctuation">,</span> file<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
    <span class="token keyword">await</span> ctx<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>file<span class="token punctuation">)</span>
    <span class="token keyword">return</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
 <span class="token punctuation">}</span>
  <span class="token comment">// 兼容 alias 情况</span>
  <span class="token keyword">const</span> importerFilePath <span class="token operator">=</span> importer <span class="token operator">?</span> resolver<span class="token punctuation">.</span><span class="token function">requestToFile</span><span class="token punctuation">(</span>importer<span class="token punctuation">)</span> <span class="token operator">:</span> root
  <span class="token keyword">const</span> nodeModulePath <span class="token operator">=</span> <span class="token function">resolveNodeModuleFile</span><span class="token punctuation">(</span>importerFilePath<span class="token punctuation">,</span> id<span class="token punctuation">)</span>
  <span class="token comment">// 如果是个 node_modules 的模块，读取文件。</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>nodeModulePath<span class="token punctuation">)</span> <span class="token punctuation">{</span>
   <span class="token keyword">return</span> <span class="token function">serve</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> nodeModulePath<span class="token punctuation">,</span> <span class="token string">'node_modules'</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre></div><ul><li><strong>依赖预构建</strong></li></ul> <p>依赖预构建主要有两个目的：</p> <ul><li><strong>CommonJS 和 UMD 兼容性:</strong> 开发阶段中，Vite 的开发服务器将所有代码视为原生 ES 模块。因此，Vite 必须先将作为 CommonJS 或 UMD 发布的依赖项转换为 ESM。</li> <li><strong>性能：</strong> Vite 将有许多内部模块的 ESM 依赖关系转换为单个模块，以提高后续页面加载性能。</li></ul> <p>Vite使用esbuild在初次启动开发服务器前把检测到的依赖进行预构建。Vite 基于ESM，在使用某些模块时，由于模块依赖了另一些模块，依赖的模块又基于另一些模块。会出现页面初始化时一次发送数百个模块请求的情况。</p> <p>以 lodash-es 为例，代码中以 import { debounce } from 'lodash' 导入一个命名函数时候，并不是只下载包含这个函数的文件，而是有一个依赖图。</p> <p><img src="https://pic3.zhimg.com/80/v2-be2c1087b01b20617c935ffbbbf18076_1440w.webp" alt="img"></p> <p>可以看到一共发送了651个请求。一共花费1.53s。</p> <p><img src="https://pic4.zhimg.com/80/v2-e07aa1b7ff76db4fba988eebf8b6a50b_1440w.webp" alt="img"></p> <p>Vite 为了优化这个情况，利用esbuild在启动的时候预先把debounce用到的所有内部模块全部打包成一个bundle，这样就浏览器在请求debounce时，便只需要发送一次请求了</p> <p>可以看到预构建后，只发送了14个请求。</p> <p><img src="https://pic2.zhimg.com/80/v2-9b49c337798e860d079c71206d13fd45_1440w.webp" alt="img"></p> <p><strong>2.2.2、静态资源加载</strong></p> <p>当请求的路径符合 imageRE, mediaRE, fontsRE 或 JSON 格式，会被认为是一个静态资源。静态资源将处理成ESM模块返回。</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// src/node/utils/pathUtils.ts</span>
<span class="token keyword">const</span> imageRE <span class="token operator">=</span> <span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">\.(png|jpe?g|gif|svg|ico|webp)(\?.*)?$</span><span class="token regex-delimiter">/</span></span>
<span class="token keyword">const</span> mediaRE <span class="token operator">=</span> <span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$</span><span class="token regex-delimiter">/</span></span>
<span class="token keyword">const</span> fontsRE <span class="token operator">=</span> <span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">\.(woff2?|eot|ttf|otf)(\?.*)?$</span><span class="token regex-delimiter">/</span><span class="token regex-flags">i</span></span>
<span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">isStaticAsset</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token literal-property property">file</span><span class="token operator">:</span> string</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> imageRE<span class="token punctuation">.</span><span class="token function">test</span><span class="token punctuation">(</span>file<span class="token punctuation">)</span> <span class="token operator">||</span> mediaRE<span class="token punctuation">.</span><span class="token function">test</span><span class="token punctuation">(</span>file<span class="token punctuation">)</span> <span class="token operator">||</span> fontsRE<span class="token punctuation">.</span><span class="token function">test</span><span class="token punctuation">(</span>file<span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token comment">// src/node/server/serverPluginAssets.ts</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isStaticAsset</span><span class="token punctuation">(</span>ctx<span class="token punctuation">.</span>path<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token function">isImportRequest</span><span class="token punctuation">(</span>ctx<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    ctx<span class="token punctuation">.</span>type <span class="token operator">=</span> <span class="token string">'js'</span>
    ctx<span class="token punctuation">.</span>body <span class="token operator">=</span> <span class="token keyword">export</span> <span class="token keyword">default</span> $<span class="token punctuation">{</span><span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>ctx<span class="token punctuation">.</span>path<span class="token punctuation">)</span><span class="token punctuation">}</span> <span class="token comment">// 输出是path</span>
    <span class="token keyword">return</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">return</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token literal-property property">jsonPlugin</span><span class="token operator">:</span> <span class="token function-variable function">ServerPlugin</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> app <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">await</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token comment">// handle .json imports</span>
    <span class="token comment">// note ctx.body could be null if upstream set status to 304</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>ctx<span class="token punctuation">.</span>path<span class="token punctuation">.</span><span class="token function">endsWith</span><span class="token punctuation">(</span><span class="token string">'.json'</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token function">isImportRequest</span><span class="token punctuation">(</span>ctx<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> ctx<span class="token punctuation">.</span>body<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      ctx<span class="token punctuation">.</span>type <span class="token operator">=</span> <span class="token string">'js'</span>
      ctx<span class="token punctuation">.</span>body <span class="token operator">=</span> <span class="token function">dataToEsm</span><span class="token punctuation">(</span><span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">await</span> <span class="token function">readBody</span><span class="token punctuation">(</span>ctx<span class="token punctuation">.</span>body<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token operator">!</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
        <span class="token literal-property property">namedExports</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
        <span class="token literal-property property">preferConst</span><span class="token operator">:</span> <span class="token boolean">true</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre></div><p><strong>2.2.3、vue文件缓存</strong></p> <p>当 Vite 遇到一个 .vue 后缀的文件时。由于 .vue 模板文件的特殊性，它被拆分成 template, css, script 模块三个模块进行分别处理。最后会对 script, template, css 发送多个请求获取</p> <p><img src="https://pic3.zhimg.com/80/v2-f950909415cbf8394b3ed7a5706be05e_1440w.webp" alt="img"></p> <p>如上图中请求 App.vue 获取script 代码 , App.vue?type=template 获取 template, App.vue?type=style。这些代码都被插入在 App.vue 返回的代码中。</p> <p><img src="https://pic4.zhimg.com/80/v2-bc884d535bcdc93c4eb5feaf35f417bb_1440w.webp" alt="img"></p> <p><strong>2.2.4、 js/ts处理</strong></p> <p>Vite使用esbuild将ts转译到js，约是tsc速度的20～30倍，同时HMR更新反应到浏览器的时间会小于50ms。但是，由于esbuild转换ts到js对于类型操作仅仅是擦除，所以完全保证不了类型正确，因此需要额外校验类型，比如使用tsc --noEmit。</p> <p>将ts转换成js后，浏览器便可以利用ESM直接拿到js资源。</p> <h3 id="_2-3、-热更新原理"><a href="#_2-3、-热更新原理" class="header-anchor">#</a> 2.3、 热更新原理</h3> <p>Vite 的热加载原理，其实就是在客户端与服务端建立了一个 websocket 连接，当代码被修改时，服务端发送消息通知客户端去请求修改模块的代码，完成热更新。</p> <ul><li>服务端：服务端做的就是监听代码文件的改变，在合适的时机向客户端发送 websocket 信息通知客户端去请求新的模块代码。</li> <li>客户端：Vite 中客户端的 websocket 相关代码在处理 html 中时被写入代码中。可以看到在处理 html 时，vite/client 的相关代码已经被插入。</li></ul> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">export</span> <span class="token keyword">const</span> clientPublicPath <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/vite/client</span><span class="token template-punctuation string">`</span></span>
<span class="token keyword">const</span> devInjectionCode <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">\n&lt;script type=&quot;module&quot;&gt;import &quot;</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>clientPublicPath<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">&quot;&lt;/script&gt;\n</span><span class="token template-punctuation string">`</span></span>
<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">rewriteHtml</span><span class="token punctuation">(</span><span class="token parameter"><span class="token literal-property property">importer</span><span class="token operator">:</span> string<span class="token punctuation">,</span> <span class="token literal-property property">html</span><span class="token operator">:</span> string</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token function">injectScriptToHtml</span><span class="token punctuation">(</span>html<span class="token punctuation">,</span> devInjectionCode<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre></div><p>当request.path 路径是 /vite/client 时，请求获取已经提前写好的关于 websocket 的代码。因此在客户端中我们创建了一个 websocket 服务并与服务端建立了连接。</p> <p>Vite 会接受到来自客户端的消息。通过不同的消息触发一些事件。做到浏览器端的即时热模块更换（热更新）。包括 connect、vue-reload、vue-rerender 等事件，分别触发组件vue 的重新加载，render等。</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// Listen for messages</span>
socket<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'message'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> data <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> payload <span class="token operator">=</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token keyword">as</span> HMRPayload <span class="token operator">|</span> MultiUpdatePayload
  <span class="token keyword">if</span> <span class="token punctuation">(</span>payload<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">'multi'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    payload<span class="token punctuation">.</span>updates<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>handleMessage<span class="token punctuation">)</span>
  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
    <span class="token function">handleMessage</span><span class="token punctuation">(</span>payload<span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">handleMessage</span><span class="token punctuation">(</span><span class="token parameter"><span class="token literal-property property">payload</span><span class="token operator">:</span> HMRPayload</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">{</span> path<span class="token punctuation">,</span> changeSrcPath<span class="token punctuation">,</span> timestamp <span class="token punctuation">}</span> <span class="token operator">=</span> payload <span class="token keyword">as</span> UpdatePayload
  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>path<span class="token punctuation">)</span>
  <span class="token keyword">switch</span> <span class="token punctuation">(</span>payload<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">case</span> <span class="token string">'connected'</span><span class="token operator">:</span>
      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">[vite] connected.</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
      <span class="token keyword">break</span>
    <span class="token keyword">case</span> <span class="token string">'vue-reload'</span><span class="token operator">:</span>
      <span class="token function">queueUpdate</span><span class="token punctuation">(</span>
        <span class="token keyword">import</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>path<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">?t=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>timestamp<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
          <span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">err</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">warnFailedFetch</span><span class="token punctuation">(</span>err<span class="token punctuation">,</span> path<span class="token punctuation">)</span><span class="token punctuation">)</span>
          <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">m</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
            __VUE_HMR_RUNTIME__<span class="token punctuation">.</span><span class="token function">reload</span><span class="token punctuation">(</span>path<span class="token punctuation">,</span> m<span class="token punctuation">.</span>default<span class="token punctuation">)</span>
            console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">[vite] </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>path<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> reloaded.</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
          <span class="token punctuation">}</span><span class="token punctuation">)</span>
      <span class="token punctuation">)</span>
      <span class="token keyword">break</span>
    <span class="token keyword">case</span> <span class="token string">'vue-rerender'</span><span class="token operator">:</span>
      <span class="token keyword">const</span> templatePath <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>path<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">?type=template</span><span class="token template-punctuation string">`</span></span>
      <span class="token keyword">import</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>templatePath<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">&amp;t=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>timestamp<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">m</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        __VUE_HMR_RUNTIME__<span class="token punctuation">.</span><span class="token function">rerender</span><span class="token punctuation">(</span>path<span class="token punctuation">,</span> m<span class="token punctuation">.</span>render<span class="token punctuation">)</span>
        console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">[vite] </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>path<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> template updated.</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span>
      <span class="token keyword">break</span>
    <span class="token keyword">case</span> <span class="token string">'style-update'</span><span class="token operator">:</span>
      <span class="token comment">// check if this is referenced in html via &lt;link&gt;</span>
      <span class="token keyword">const</span> el <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">link[href*='</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>path<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">']</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>el<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        el<span class="token punctuation">.</span><span class="token function">setAttribute</span><span class="token punctuation">(</span>
          <span class="token string">'href'</span><span class="token punctuation">,</span>
          <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>path<span class="token interpolation-punctuation punctuation">}</span></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>path<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token string">'?'</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token string">'&amp;'</span> <span class="token operator">:</span> <span class="token string">'?'</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">t=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>timestamp<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span>
        <span class="token punctuation">)</span>
        <span class="token keyword">break</span>
      <span class="token punctuation">}</span>
      <span class="token keyword">const</span> importQuery <span class="token operator">=</span> path<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token string">'?'</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token string">'&amp;import'</span> <span class="token operator">:</span> <span class="token string">'?import'</span>
      <span class="token keyword">await</span> <span class="token keyword">import</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>path<span class="token interpolation-punctuation punctuation">}</span></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>importQuery<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">&amp;t=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>timestamp<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">[vite] </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>path<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> updated.</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
      <span class="token keyword">break</span>
    <span class="token keyword">case</span> <span class="token string">'js-update'</span><span class="token operator">:</span>
      <span class="token function">queueUpdate</span><span class="token punctuation">(</span><span class="token function">updateModule</span><span class="token punctuation">(</span>path<span class="token punctuation">,</span> changeSrcPath<span class="token punctuation">,</span> timestamp<span class="token punctuation">)</span><span class="token punctuation">)</span>
      <span class="token keyword">break</span>
    <span class="token keyword">case</span> <span class="token string">'custom'</span><span class="token operator">:</span>
      <span class="token keyword">const</span> cbs <span class="token operator">=</span> customUpdateMap<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>payload<span class="token punctuation">.</span>id<span class="token punctuation">)</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>cbs<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        cbs<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">cb</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">cb</span><span class="token punctuation">(</span>payload<span class="token punctuation">.</span>customData<span class="token punctuation">)</span><span class="token punctuation">)</span>
      <span class="token punctuation">}</span>
      <span class="token keyword">break</span>
    <span class="token keyword">case</span> <span class="token string">'full-reload'</span><span class="token operator">:</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>path<span class="token punctuation">.</span><span class="token function">endsWith</span><span class="token punctuation">(</span><span class="token string">'.html'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// if html file is edited, only reload the page if the browser is</span>
        <span class="token comment">// currently on that page.</span>
        <span class="token keyword">const</span> pagePath <span class="token operator">=</span> location<span class="token punctuation">.</span>pathname
        <span class="token keyword">if</span> <span class="token punctuation">(</span>
          pagePath <span class="token operator">===</span> path <span class="token operator">||</span>
          <span class="token punctuation">(</span>pagePath<span class="token punctuation">.</span><span class="token function">endsWith</span><span class="token punctuation">(</span><span class="token string">'/'</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> pagePath <span class="token operator">+</span> <span class="token string">'index.html'</span> <span class="token operator">===</span> path<span class="token punctuation">)</span>
        <span class="token punctuation">)</span> <span class="token punctuation">{</span>
          location<span class="token punctuation">.</span><span class="token function">reload</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token punctuation">}</span>
        <span class="token keyword">return</span>
      <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
       location<span class="token punctuation">.</span><span class="token function">reload</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
      <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><h2 id="三、问题"><a href="#三、问题" class="header-anchor">#</a> 三、问题</h2> <p>1、构建工具和打包工具的区别？</p> <blockquote><p>构建过程应该包括 预编译、语法检查、词法检查、依赖处理、文件合并、文件压缩、单元测试、版本管理等 。打包工具更注重打包这一过程，主要包括依赖管理和版本管理。</p></blockquote> <p>2、Vite有什么缺点？</p> <ul><li>目前 Vite 还是使用的 es module 模块不能直接使用生产环境（兼容性问题）。默认情况下，无论是 dev 还是 build 都会直接打出 ESM 版本的代码包，这就要求客户浏览器需要有一个比较新的版本，这放在现在的国情下还是有点难度的。不过 Vite 同时提供了一些弥补的方法，使用 build.polyfillDynamicImport 配置项配合 @vitejs/plugin-legacy 打包出一个看起来兼容性比较好的版本。</li> <li>生产环境使用 rollup 打包会造成开发环境与生产环境的不一致。</li> <li>很多 第三方 sdk 没有产出 ems 格式的的代码，这个需要自己去做一些兼容。</li></ul> <p>3、Vite生产环境用了Rollup，那能在生产环境中直接使用 esm 吗？</p> <ul><li>其实目前的主要问题可能还是兼容性问题。</li> <li>如果你的项目不需要兼容 IE11 等低版本的浏览器，自然是可以使用的。</li> <li>但是更通用的方案可能还是类似 ployfill.io 的原理实现， 提前构建好 bundle.js 与 es module 两个版本的代码，根据浏览器的实际兼容性去动态选择导入哪个模块。</li></ul> <p>4、对于一些 没有产出 commonjs 的模块，如何去兼容呢？</p> <p>首先业界是有一些如 lebab 的方法可以将 commjs 代码快速转化为 esm 的，但是对于一些格式不规范的代码，可能还是需要单独处理。</p> <p>5、如果组件嵌套层级比较深，会影响速度吗？</p> <ul><li>可以看到请求 lodash 时 651 个请求只耗时 1.53s。这个耗时是完全可以接受的。</li> <li>Vite 是完全按需加载的，在页面初始化时只会请求初始化页面的一些组件，也就是说即使层级深，但如果未展示可以不加载。</li> <li>缓存可以降低耗时</li></ul></div> <footer class="page-edit"><!----> <!----></footer> <div class="page-nav"><p class="inner"><span class="prev">
      ←
      <a href="/blog/archives/httpcode.html" class="prev">
        HTTP 状态码
      </a></span> <!----></p></div> </main></div><div class="global-ui"></div></div>
    <script src="/blog/assets/js/app.43c5da00.js" defer></script><script src="/blog/assets/js/2.d3a12b6d.js" defer></script><script src="/blog/assets/js/1.f8bb34da.js" defer></script><script src="/blog/assets/js/31.665d02a7.js" defer></script>
  </body>
</html>
